package searcher;

import org.ansj.domain.Term;
import org.ansj.splitWord.analysis.ToAnalysis;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.*;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: HUAWEI
 * Date: 2023-09-06
 * Time: 14:22
 */

//通过这个类来完成整个搜索过程
public class DocSearcher {
    public static final String STOP_WORD_PATH = "C:\\111";
    private HashSet<String> stopwords = new HashSet<>();
    //此处要加上索引对象的实例；
    //同时要完成索引加载的工作
    private Index index = new Index();

    public DocSearcher() {
        index.load();
        loadStopWords();
    }

    //提供一个方法
    //完成整个搜索过程的方法 参数（输入部分）就是用户给出的查询词
    //返回值（输出部分）就是搜索的集合
    public List<Result> search(String query) {
        //1.[分词] 针对 query 这个查询词进行分词
        List<Term> oldterms = ToAnalysis.parse(query).getTerms();
        List<Term> terms = new ArrayList<>();
        //针对分词结果，使用暂停词表进行过滤
        for(Term term : oldterms) {
            if(stopwords.contains(term.getName())) {
                continue;
            }
            terms.add(term);
        }


        //2.[出发] 针对分词结果倒排
        ArrayList<List<Weight>> termResult = new ArrayList<>();//termResult是个二位数组
        List<Weight> allTermResult = new ArrayList<>();
        for (Term term : terms) {
            //查倒排
            String word = term.getName();
            List<Weight> invertedList = index.getInverted(word);
            if (invertedList == null) {
                //说明这个词在所有文档中都不存在
                continue;
                //如果不存在，要进行汇总；
            }
            allTermResult.addAll(invertedList);

        }
        //3.[合并]针对多个分词结果触发的相同文档，进行权重合并
        List<Weight> allTermResult = mergeResult (termResult);//合并
        //4.[排序] 针对触发的结果按照权重降序排序
        allTermResult.sort(new Comparator<Weight>() {
            @Override
            public int compare(Weight o1, Weight o2) {
                //如果是升序排序：return o1.getWeight() - o2.getWeight();
                //如果是降序排序：return o2.getWeight() - o1.getWeight();
                return o2.getWeight() - o1.getWeight();
            }
        });
        //5.[包装结果] 针对排序的结果，去查正排，构造出要返回的数据
        List<Result> results = new ArrayList<>();
        for (Weight weight : allTermResult) {
            DocInfo docInfo = index.getDocInfo(weight.getDocId());
            Result result = new Result();
            result.setTitle(docInfo.getTitle());
            result.setUrl(docInfo.getUrl());
            result.setDesc(GenDesc(docInfo.getContent(), terms));
            results.add(result);
        }
        return results;
    }
    //通过这个内部类来描述一个元素在二维数组中的位置
    static class Pos {
        public int row;
        public int col;
        //然后来一个构造方法Generate
        public Pos(int row, int col) {
            this.row = row;
            this.col = col;
        }
    }

    private List<Weight> mergeResult(ArrayList<List<Weight>> source) {
        //进行合并的时候，是把多个行合并成一行了，
        //合并的过程中势必是需要操作这个这个二维数组（二维List)里面每一个元素的
        //操作元素就涉及到“行，列”这样的概念。要确定二维数组中的一个元素，就需要明确 行和列

        //1.先针对每一行进行排序(按照id进行升序排序）
        for(List<Weight> curRow : source) {
            //这里遍历的每一个结果就是我们取出的每一行
            curRow.sort(new Comparator<Weight>() {
                @Override
                public int compare(Weight o1, Weight o2) {
                    return o1.getDocId() - o2.getDocId();//升序是o1 - o2. 降序是o2-o1
                }
            });
        }
        //2.1借助一个优先队列，针对这些行就行合并
        //target 表示合并的结果，再搞一个优先队列
        List<Weight> target = new ArrayList<>();
        //优先队列 Pos 是上面所描述的行和列
        PriorityQueue<Pos> queue = new PriorityQueue<>(new Comparator<Pos>() {
            @Override
            public int compare(Pos o1, Pos o2) {
                //先根据这里的pos值找到对应的Weight 对象，再根据Weight对应的docId 来进行排序
                //优先队列存在的意义就是为了找出每一行对应的最小的docId对应的Weight的对象
                //把最小的对象插入的target中，同时把对应的下标往后移动 docId 小的更优先
                //分来写return source.get(o1.row) - source.get(o1.col);
                Weight w1 = source.get(o1.row) - source.get(o1.col);
                Weight w2 = source.get(o2.row) - source.get(o2.col);
                return w1.getDocId() - w2.getDocId();
            }
        }); //里面再来一个比较规则
        //2.2初始化队列，把每一行的第一个元素放到队列中
        for(int row = 0;row <source.size();row++) {
            //初始插入的元素的col就是0；
            queue.offer(new Pos(row,0));
        //2.3循环的取队首元素（也就是当前这若干行中的最小元素
        while(!queue.isEmpty()) {
            Pos minPos = queue.poll();
            Weight curWeight = source.get(minPos.row).get(minPos.col);
        }
        //2.4看看这个取到的Weight是否和前一个插入到target中的结果是相同的docId
        //如果是就合并
        if(target.size() > 0) {
            //取出来上次出入元素
            Weight lastWeight = target.get(target.size() - 1);

            if(lastWeight.getDocId() == curWeight.getDocId) {
                //遇到相同的文档就合并权重
                lastWeight.setWeight(lastWeight.getWeight() + curWeight.getWeight());
            } else {
                //如果文档iD不相同，就把curweight插入到文档 target的末尾
                target.add(curWeight);
            }
        } else {
            //如果target是空的，就直接插入即可
            target.add(curWeight);
        }
        //2.5当前元素处理完了之后，要把对应这个元素的光标往后移动，去取这一行的下一个元素
        Pos newPos = new Pos(minPos.row,minPos.col+1);
        if (newPos.col >= source.get(newPos.row).size()) {
            //如果移动光标后超过这一行的列数，就说明到达末尾了
            //就说明这一行处理完了
            continue;
        }
        queue.offer(newPos);
        }
        return target;

    }

    //方法
    private String GenDesc(String content, List<Term> terms) {
        //先遍历分词结果，看看哪个结果是在content中存在的
        int firstPos = -1;
        for (Term term : terms) {
            //别忘了，分词库直接针对词进行转小写了
            //正因为如此，就必须把正文转成小写再进行查询
            String word = term.getName();
            //此处需要“全字匹配”，让word能够独立成词，才要查找出来，而不是只做词的一部分
            //此处的"全字匹配"并不是很严谨，更严谨的做法是"正则表达式"
            firstPos = content.toLowerCase().indexOf(" " + word + " ");
            if (firstPos >= 0) {
                //找到了位置
                break;
            }
        }
        //后面还需要用到firstPos
        if (firstPos == -1) {
            //所有的分词结果都不在正文中存在.
            //因为这是属于比较极端的情况；
            //此时直接返回一个空的描述
            //或者也可以直接取正文的前160个字符作为描述；
            if(content.length()>160) {
                return content.substring(0, 160) + "...";
            }
            return content;
        }
            //从firstPos作为基准位置，往前找60个字符，作为描述的起始位置
            String desc = "";
            int descBeg = firstPos < 60 ? 0 : firstPos - 60;
            if (descBeg + 160 > content.length()) {
                desc = content.substring(descBeg);
            } else {
                desc = content.substring(descBeg, descBeg + 160) + "...";
            }

            //在此处加上一个替换操作，把描述中的部分和结果相同的部分加上一层<i>标签，就可以通过replace的方式来实现给搜索词标红
        for(Term term : terms) {
            String word = term.getName();
            desc = desc.replaceAll("<?i>" + word + "","<i> " + word +"< /i>");
        }
            return desc;
        }

    public void loadStopWords(){
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(STOP_WORD_PATH))) {
         while (true) {
             String line =bufferedReader.readLine();
             if(line == null) {
                 //读取文件完毕
                 break;
             }
             stopwords.add(line);
         }
        }catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        DocSearcher docSearcher = new DocSearcher();
        Scanner scanner = new Scanner(System.in);//标准输入
        while (true) {
            System.out.println("-> ");
            String query = scanner.next();
            List<Result> results = docSearcher.search(query);
            for(Result result : results) {
                System.out.println("===========================");
                System.out.println(result);
            }
        }

    }
}


